/*
 * prueba_hash.c
 * Pruebas para el tipo de dato abstracto Tabla de Hash
 * Copyright: (2011) Margarita Manterola, Maximiliano Curia
 * Licencia: CC-BY-SA 2.5 (ar) ó CC-BY-SA 3.0
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include "hash.h"
#define CHAR_MESSAGE '~'
#define CHAR_TITLE '#'
#define TITLE_LENGTH 10
#define AVG_LINE 50

/* ******************************************************************
 *                      FUNCIONES AUXILIARES
 * *****************************************************************/

/* Función auxiliar para imprimir si estuvo OK o no. */
void print_test(char* name, bool result)
{
    printf("%70.70s: %s\n", name, result? "OK" : "ERROR");
}

//Imprime el encabezado de un menu cuyo título recibe por parámetro.
void print_menu_header(char* title){

	char* character=malloc((1+TITLE_LENGTH)*sizeof(char));

	int i;

	for(i=0;i<TITLE_LENGTH;i++) character[i]=CHAR_TITLE;

	character[i]='\0';
	
	printf("\n%s %s %s\n",character,title,character);

	free(character);
	
	printf("\n");
	
}

//Imprime el pie de un menu cuyo título recibe por parámetro.
void print_menu_footer(char* title){

	char* spaces=malloc((1+strlen(title))*sizeof(char));

	int i;

	for(i=0;i<strlen(title);i++) spaces[i]=' ';

	spaces[i]='\0';

	print_menu_header(spaces);

	free(spaces);
	
}


/* ******************************************************************
 *                        PRUEBAS UNITARIAS
 * *****************************************************************/


/* Prueba que las primitivas de la lista funcionen correctamente. */
void prueba_crear_hash_vacio()
{
	print_menu_header("Prueba crear hash vacio");
	hash_t* hash = hash_crear(NULL);
	
	print_test("Prueba hash crear hash vacio", hash);
	print_test("Prueba hash la cantidad de elementos es 0", hash_cantidad(hash) == 0);
	print_test("Prueba hash obtener clave A, es NULL, no existe", !hash_obtener(hash, "A"));
	print_test("Prueba hash pertenece clave A, es false, no existe", !hash_pertenece(hash, "A"));
	print_test("Prueba hash borrar clave A, es NULL, no existe", !hash_borrar(hash, "A"));

	hash_destruir(hash);
	print_menu_footer("Prueba crear hash vacio");
}

void prueba_iterar_hash_vacio()
{
	print_menu_header("Prueba iterar hash vacio");
	hash_t* hash = hash_crear(NULL);
	hash_iter_t* iter = hash_iter_crear(hash);
	print_test("Prueba hash iter crear iterador hash vacio", iter);
	print_test("Prueba hash iter esta al final", hash_iter_al_final(iter));
	print_test("Prueba hash iter avanzar es false", !hash_iter_avanzar(iter));
	print_test("Prueba hash iter ver actual es NULL", !hash_iter_ver_actual(iter));

	hash_iter_destruir(iter);
	hash_destruir(hash);
	print_menu_footer("Prueba iterar hash vacio");
}

void prueba_hash_insertar()
{
	print_menu_header("Prueba hash insertar");
	
	hash_t* hash = hash_crear(NULL);
	
	char *clave1 = "perro", *valor1 = "guau";
	char *clave2 = "gato", *valor2 = "miau";
	char *clave3 = "vaca", *valor3 = "mu";

	// Inserta 1 valor y luego lo borra 
	print_test("Prueba hash insertar clave1", hash_guardar(hash, clave1, valor1));
	print_test("Prueba hash la cantidad de elementos es 1", hash_cantidad(hash) == 1);
	print_test("Prueba hash obtener clave1 es valor1", hash_obtener(hash, clave1) == valor1);
	print_test("Prueba hash obtener clave1 es valor1", hash_obtener(hash, clave1) == valor1);
	print_test("Prueba hash pertenece clave1, es true", hash_pertenece(hash, clave1));
	print_test("Prueba hash borrar clave1, es valor1", hash_borrar(hash, clave1) == valor1);
	print_test("Prueba hash la cantidad de elementos es 0", hash_cantidad(hash) == 0);

	// Inserta otros 2 valores y no los borra (se destruyen con el hash)
	print_test("Prueba hash insertar clave2", hash_guardar(hash, clave2, valor2));
	print_test("Prueba hash la cantidad de elementos es 1", hash_cantidad(hash) == 1);
	print_test("Prueba hash obtener clave2 es valor2", hash_obtener(hash, clave2) == valor2);
	print_test("Prueba hash obtener clave2 es valor2", hash_obtener(hash, clave2) == valor2);
	print_test("Prueba hash pertenece clave2, es true", hash_pertenece(hash, clave2));

	print_test("Prueba hash insertar clave3", hash_guardar(hash, clave3, valor3));
	print_test("Prueba hash la cantidad de elementos es 2", hash_cantidad(hash) == 2);
	print_test("Prueba hash obtener clave3 es valor3", hash_obtener(hash, clave3) == valor3);
	print_test("Prueba hash obtener clave3 es valor3", hash_obtener(hash, clave3) == valor3);
	print_test("Prueba hash pertenece clave3, es true", hash_pertenece(hash, clave3));

	hash_destruir(hash);
	
	print_menu_footer("Prueba hash insertar");
}

void prueba_hash_reemplazar()
{
	print_menu_header("Prueba hash reemplazar");
	hash_t* hash = hash_crear(NULL);
	
	char *clave1 = "perro", *valor1a = "guau", *valor1b = "warf";
	char *clave2 = "gato", *valor2a = "miau", *valor2b = "meaow";

	/* Inserta 2 valores y luego los reemplaza */
	print_test("Prueba hash insertar clave1", hash_guardar(hash, clave1, valor1a));
	print_test("Prueba hash obtener clave1 es valor1a", hash_obtener(hash, clave1) == valor1a);
	print_test("Prueba hash obtener clave1 es valor1a", hash_obtener(hash, clave1) == valor1a);
	print_test("Prueba hash insertar clave2", hash_guardar(hash, clave2, valor2a));
	print_test("Prueba hash obtener clave2 es valor2a", hash_obtener(hash, clave2) == valor2a);
	print_test("Prueba hash obtener clave2 es valor2a", hash_obtener(hash, clave2) == valor2a);
	print_test("Prueba hash la cantidad de elementos es 2", hash_cantidad(hash) == 2);

	print_test("Prueba hash insertar clave1 con otro valor", hash_guardar(hash, clave1, valor1b));
	print_test("Prueba hash obtener clave1 es valor1b", hash_obtener(hash, clave1) == valor1b);
	print_test("Prueba hash obtener clave1 es valor1b", hash_obtener(hash, clave1) == valor1b);
	print_test("Prueba hash insertar clave2 con otro valor", hash_guardar(hash, clave2, valor2b));
	print_test("Prueba hash obtener clave2 es valor2b", hash_obtener(hash, clave2) == valor2b);
	print_test("Prueba hash obtener clave2 es valor2b", hash_obtener(hash, clave2) == valor2b);
	print_test("Prueba hash la cantidad de elementos es 2", hash_cantidad(hash) == 2);

	hash_destruir(hash);
	print_menu_footer("Prueba hash reemplazar");
}

void prueba_hash_reemplazar_con_destruir()
{
	
	print_menu_header("Prueba hash reemplazar con destruir");
	hash_t* hash = hash_crear(free);
	
	char *clave1 = "perro", *valor1a, *valor1b;
	char *clave2 = "gato", *valor2a, *valor2b;

	/* Pide memoria para 4 valores */
	valor1a = malloc(10 * sizeof(char));
	valor1b = malloc(10 * sizeof(char));
	valor2a = malloc(10 * sizeof(char));
	valor2b = malloc(10 * sizeof(char));

	/* Inserta 2 valores y luego los reemplaza (debe liberar lo que reemplaza) */
	print_test("Prueba hash insertar clave1", hash_guardar(hash, clave1, valor1a));
	print_test("Prueba hash obtener clave1 es valor1a", hash_obtener(hash, clave1) == valor1a);
	print_test("Prueba hash obtener clave1 es valor1a", hash_obtener(hash, clave1) == valor1a);
	print_test("Prueba hash insertar clave2", hash_guardar(hash, clave2, valor2a));
	print_test("Prueba hash obtener clave2 es valor2a", hash_obtener(hash, clave2) == valor2a);
	print_test("Prueba hash obtener clave2 es valor2a", hash_obtener(hash, clave2) == valor2a);
	print_test("Prueba hash la cantidad de elementos es 2", hash_cantidad(hash) == 2);

	print_test("Prueba hash insertar clave1 con otro valor", hash_guardar(hash, clave1, valor1b));
	print_test("Prueba hash obtener clave1 es valor1b", hash_obtener(hash, clave1) == valor1b);
	print_test("Prueba hash obtener clave1 es valor1b", hash_obtener(hash, clave1) == valor1b);
	print_test("Prueba hash insertar clave2 con otro valor", hash_guardar(hash, clave2, valor2b));
	print_test("Prueba hash obtener clave2 es valor2b", hash_obtener(hash, clave2) == valor2b);
	print_test("Prueba hash obtener clave2 es valor2b", hash_obtener(hash, clave2) == valor2b);
	print_test("Prueba hash la cantidad de elementos es 2", hash_cantidad(hash) == 2);

	/* Se destruye el hash (se debe liberar lo que quedó dentro) */
	hash_destruir(hash);
	print_menu_footer("Prueba hash reemplazar con destruir");
}

void prueba_hash_borrar()
{
	
	print_menu_header("Prueba hash borrar");
	hash_t* hash = hash_crear(NULL);
	
	char *clave1 = "perro", *valor1 = "guau";
	char *clave2 = "gato", *valor2 = "miau";
	char *clave3 = "vaca", *valor3 = "mu";

	/* Inserta 3 valores y luego los borra */
	print_test("Prueba hash insertar clave1", hash_guardar(hash, clave1, valor1));
	print_test("Prueba hash insertar clave2", hash_guardar(hash, clave2, valor2));
	print_test("Prueba hash insertar clave3", hash_guardar(hash, clave3, valor3));

	/* Al borrar cada elemento comprueba que ya no está pero los otros sí. */
	print_test("Prueba hash pertenece clave3, es verdadero", hash_pertenece(hash, clave3));
	print_test("Prueba hash borrar clave3, es valor3", hash_borrar(hash, clave3) == valor3);
	print_test("Prueba hash borrar clave3, es NULL", !hash_borrar(hash, clave3));
	print_test("Prueba hash pertenece clave3, es falso", !hash_pertenece(hash, clave3));
	print_test("Prueba hash obtener clave3, es NULL", !hash_obtener(hash, clave3));
	print_test("Prueba hash la cantidad de elementos es 2", hash_cantidad(hash) == 2);

	print_test("Prueba hash pertenece clave1, es verdadero", hash_pertenece(hash, clave1));
	print_test("Prueba hash borrar clave1, es valor1", hash_borrar(hash, clave1) == valor1);
	print_test("Prueba hash borrar clave1, es NULL", !hash_borrar(hash, clave3));
	print_test("Prueba hash pertenece clave1, es falso", !hash_pertenece(hash, clave1));
	print_test("Prueba hash obtener clave1, es NULL", !hash_obtener(hash, clave1));
	print_test("Prueba hash la cantidad de elementos es 1", hash_cantidad(hash) == 1);

	print_test("Prueba hash pertenece clave2, es verdadero", hash_pertenece(hash, clave2));
	print_test("Prueba hash borrar clave2, es valor2", hash_borrar(hash, clave2) == valor2);
	print_test("Prueba hash borrar clave2, es NULL", !hash_borrar(hash, clave3));
	print_test("Prueba hash pertenece clave2, es falso", !hash_pertenece(hash, clave2));
	print_test("Prueba hash obtener clave2, es NULL", !hash_obtener(hash, clave2));
	print_test("Prueba hash la cantidad de elementos es 0", hash_cantidad(hash) == 0);

	hash_destruir(hash);
	print_menu_footer("Prueba hash borrar");
}

void prueba_hash_clave_vacia()
{
	print_menu_header("Prueba hash clave vacia");
	hash_t* hash = hash_crear(NULL);
	
	char *clave = "", *valor = "";

	print_test("Prueba hash insertar clave vacia", hash_guardar(hash, clave, valor));
	print_test("Prueba hash la cantidad de elementos es 1", hash_cantidad(hash) == 1);
	print_test("Prueba hash obtener clave vacia es valor", hash_obtener(hash, clave) == valor);
	print_test("Prueba hash pertenece clave vacia, es true", hash_pertenece(hash, clave));
	print_test("Prueba hash borrar clave vacia, es valor", hash_borrar(hash, clave) == valor);
	print_test("Prueba hash la cantidad de elementos es 0", hash_cantidad(hash) == 0);

	hash_destruir(hash);
	print_menu_footer("Prueba hash clave vacia");
}

void prueba_hash_valor_null()
{
	
	print_menu_header("Prueba hash valor NULL");
	hash_t* hash = hash_crear(NULL);
	
	char *clave = "", *valor = NULL;

	/* Inserta 1 valor y luego lo borra */
	print_test("Prueba hash insertar clave vacia valor NULL", hash_guardar(hash, clave, valor));
	print_test("Prueba hash la cantidad de elementos es 1", hash_cantidad(hash) == 1);
	print_test("Prueba hash obtener clave vacia es valor NULL", hash_obtener(hash, clave) == valor);
	print_test("Prueba hash pertenece clave vacia, es true", hash_pertenece(hash, clave));
	print_test("Prueba hash borrar clave vacia, es valor NULL", hash_borrar(hash, clave) == valor);
	print_test("Prueba hash la cantidad de elementos es 0", hash_cantidad(hash) == 0);

	hash_destruir(hash);
	print_menu_footer("Prueba hash valor NULL");
}

void prueba_hash_volumen() 
{
	
	print_menu_header("Prueba hash volumen");
	hash_t* hash = hash_crear(free);
	size_t largo = 10000;

	char* claves[largo];
	int* valores[largo];

	/* Inserta 'largo' parejas en el hash */
	bool ok = true;
	for (size_t i = 0; i < largo; i++) {
		claves[i] = malloc(10*sizeof(char));
		valores[i] = malloc(sizeof(int));
		sprintf(claves[i], "%08d", (int)i);
		*valores[i] = i;
		ok = hash_guardar(hash, claves[i], valores[i]);
		if (!ok) break;
	}
	print_test("Prueba hash almacenar 10000 elementos", ok);
	print_test("Prueba hash la cantidad de elementos es 10000", hash_cantidad(hash) == largo);

	/* Verifica que devuelva los valores correctos */	
	for (size_t i = 0; i < largo; i++) {
		ok = hash_pertenece(hash, claves[i]);
		if (!ok) break;
		ok = hash_obtener(hash, claves[i]) == valores[i];
		if (!ok) break;
	}

	print_test("Prueba hash pertenece y obtener 10000 elementos", ok);
	print_test("Prueba hash la cantidad de elementos es 10000", hash_cantidad(hash) == largo);

	/* Verifica que borre y devuelva los valores correctos */
	for (size_t i = 0; i < largo; i++) {
		ok = hash_borrar(hash, claves[i]) == valores[i];
		if (!ok) break;
	}

	print_test("Prueba hash borrar 10000 elementos", ok);
	print_test("Prueba hash la cantidad de elementos es 0", hash_cantidad(hash) == 0);

	/* Destruye el hash y crea uno nuevo que sí libera */
	hash_destruir(hash);
	hash = hash_crear(free);

	/* Inserta 'largo' parejas en el hash */
	ok = true;
	for (size_t i = 0; i < largo; i++) {
		ok = hash_guardar(hash, claves[i], valores[i]);
		if (!ok) break;
	}

	/* Libera las cadenas */
	for (size_t i = 0; i < largo; i++) {
		free(claves[i]);
	}
	
	/* Destruye el hash - debería liberar los enteros */
	hash_destruir(hash);
	print_menu_footer("Prueba hash volumen");
}

int buscar(const char* clave, char* claves[], size_t largo) 
{	
	for (size_t i = 0; i < largo; i++) {
		if (strcmp(clave, claves[i]) == 0) return i;
	}
	return -1;
}

void prueba_hash_iterar()
{
	print_menu_header("Prueba hash iterar");
	hash_t* hash = hash_crear(NULL);
	
	char *claves[] = {"perro", "gato", "vaca"};
	char *valores[] = {"guau", "miau", "mu"};

	// Inserta 3 valores 
	print_test("Prueba hash insertar clave1", hash_guardar(hash, claves[0], valores[0]));
	print_test("Prueba hash insertar clave2", hash_guardar(hash, claves[1], valores[1]));
	print_test("Prueba hash insertar clave3", hash_guardar(hash, claves[2], valores[2]));

	// Prueba de iteración sobre las claves almacenadas.
	hash_iter_t* iter = hash_iter_crear(hash);
	const char *clave;
	int indice;
	
	print_test("Prueba hash iterador esta al final, es false", !hash_iter_al_final(iter));

	// Primer valor 
	clave = hash_iter_ver_actual(iter);
	indice = buscar(clave, claves, sizeof(claves));
	print_test("Prueba hash iterador ver actual, es una clave valida", indice != -1);
	print_test("Prueba hash iterador ver actual, no es el mismo puntero", clave != claves[indice]);
	print_test("Prueba hash iterador avanzar es true", hash_iter_avanzar(iter));
	print_test("Prueba hash iterador esta al final, es false", !hash_iter_al_final(iter));

	// Segundo valor 
	clave = hash_iter_ver_actual(iter);
	indice = buscar(clave, claves, sizeof(claves));
	print_test("Prueba hash iterador ver actual, es una clave valida", indice != -1);
	print_test("Prueba hash iterador ver actual, no es el mismo puntero", clave != claves[indice]);
	print_test("Prueba hash iterador avanzar es true", hash_iter_avanzar(iter));
	print_test("Prueba hash iterador esta al final, es false", !hash_iter_al_final(iter));

	// Tercer valor 
	clave = hash_iter_ver_actual(iter);
	indice = buscar(clave, claves, sizeof(claves));
	print_test("Prueba hash iterador ver actual, es una clave valida", indice != -1);
	print_test("Prueba hash iterador ver actual, no es el mismo puntero", clave != claves[indice]);
	print_test("Prueba hash iterador avanzar es false", !hash_iter_avanzar(iter));
	print_test("Prueba hash iterador esta al final, es true", hash_iter_al_final(iter));

	// Vuelve a tratar de avanzar, por las dudas 
	print_test("Prueba hash iterador ver actual, es NULL", !hash_iter_ver_actual(iter));
	print_test("Prueba hash iterador avanzar es false", !hash_iter_avanzar(iter));
	print_test("Prueba hash iterador esta al final, es true", hash_iter_al_final(iter));

	hash_iter_destruir(iter);
	hash_destruir(hash);
	print_menu_footer("Prueba hash iterar");
	
}

/* ******************************************************************
 *                        PROGRAMA PRINCIPAL
 * *****************************************************************/

/* Programa principal. */
int main(void)
{
    /* Ejecuta todas las pruebas unitarias. */
   
    prueba_crear_hash_vacio();
    prueba_iterar_hash_vacio();
	prueba_hash_insertar();
	prueba_hash_reemplazar();
	prueba_hash_reemplazar_con_destruir();
	prueba_hash_borrar();
	prueba_hash_volumen();
	prueba_hash_iterar();

    return 0;
}
